#include "Log.h"
#include <iostream>
#include <fstream>
#include <stdio.h>

std::ostream           *Log::output = &std::cerr;
std::ofstream           Log::output_fstream;
bool                    Log::outputting_to_file = false;
std::stack<std::string> Log::function_names;
std::stack<int>         Log::function_details;
int                     Log::active_details = 0;
bool                    Log::show_time_stamp = true;
bool                    Log::show_log_detail = true;
std::string             *detail_names;
int                     max_detail_name_len;

Log::Log() {
    // std::cerr << "entering Log::Log() (ie constructor)\n";
//    output = &(std::cerr);
}

void Log::Entering(log_detail_t detail,
                   const std::string& name) {
    // std::cerr << "Entering(" << detail << "," << name << ")\n";

    Log::function_names.push(name);
    Log::function_details.push(detail);

    // std::cerr << "pushed\n";

    if (1 << detail & Log::active_details)
        Log().Get(detail) << ">> Entering " << name;
}

void Log::Exiting() {
    if (!function_names.empty()) {
        std::string name = Log::function_names.top();

        log_detail_t detail = (log_detail_t)Log::function_details.top();

        if (1 << (int)detail & Log::active_details)
            Log().Get(detail) << "<< Leaving " << name;

        Log::function_names.pop();
        Log::function_details.pop();
    }
}

std::ostringstream& Log::Get(log_detail_t detail) {
    // std::cerr << "entering Log::Get(" << detail << ")\n";

    if (Log::show_time_stamp)
        os << "- " << NowTime();

    if (Log::show_log_detail) {
        std::string detail_name = ToString(detail);
        int len = detail_name.length();
        for (int j=len; j<max_detail_name_len; j++)
            detail_name += std::string(" ");
        os << " " << detail_name << ": ";
    }

    int indent_level = function_names.size();
    for (int i=0; i<indent_level; i++)
        os << "  ";
//    os << std::string(level > logDEBUG ? level - logDEBUG : 0, '\t');

    // std::cerr << "Get: os=" << os << "\n";
    // std::cerr << "Get: output=" << Log::output << "\n";
    // std::cerr.flush();

    // std::cerr << "Get: trying hello\n";
    // (*Log::output) << "hello again!\n";
    // std::cerr << "Get: hello OK\n";
    // std::cerr.flush();


    return os;
}

Log::~Log() {
    // std::cerr << "entering ~Log()\n";
    // std::cerr << "os now has \"" << os.str() << "\"\n";
    // std::cerr << "output=" << Log::output << "\n";
    // std::cerr.flush();

    os << std::endl;

    (*output) << os.str();
    output->flush();

    // std::cerr << "sent os to output\n";
    // std::cerr.flush();
}

void Log::set_stream_file(const std::string& filename) {
    Log::output_fstream.open(filename);
    output = &output_fstream;
}

void Log::set_output_stream(std::ostream& stream) {
    // std::cerr << "entering Log::set_output_stream()\n";

    output = &stream;
}

// Here we translate the log flags from enums to strings, using
// the macro token stringize operator #.  See Log.h for details on MY_ENUM

std::string Log::ToString(log_detail_t detail) {
    // std::cerr << "entering Log::ToString(" << detail << ")\n";

    static bool first_time = true;

    if (first_time) {
        first_time = false;
#define MY_ENUM(x) #x,
        static const char* const buffer[] = {
#include "Log_Details.inc"
        };
#undef MY_ENUM

        max_detail_name_len = -1;
        int n = LOG_DETAIL_N;
        for (int i=0; i<n; i++) {
            int len = strlen(buffer[i]);
            if (len > max_detail_name_len)
                max_detail_name_len = len;
        }

        detail_names = new std::string[n];
        for (int i=0; i<n; i++) {
            detail_names[i] = std::string(buffer[i]);
        }
    }

    // std::cerr << "detail_names[" << detail << "]='" << detail_names[detail] << "'\n";

    return detail_names[detail];
}

log_detail_t Log::FromString(const std::string& detail) {
    // std::cerr << "entering Log::FromString(\"" << detail << "\")\n";

    for (int i=0; i<LOG_DETAIL_N; i++)
        if (Log::ToString((log_detail_t)i) == detail)
            return (log_detail_t)i;

    Log().Get((log_detail_t)1) << "Unknown log detail '" << detail << "'. Using detail N as default.";
    return (log_detail_t)LOG_DETAIL_N;
}

int Log::BitFromDetail(log_detail_t detail) {
#define MY_ENUM(x) LOG_DETAIL_BIT_##x,
    static const int bits[] = {
#include "Log_Details.inc"
    };
#undef MY_ENUM

    // std::cerr << "BitFromDetail: detail=" << detail << "\n";
    // std::cerr << "BitFromDetail: bit=" << bits[detail] << "\n";

    return bits[detail];
}

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__)

#include <windows.h>

std::string Log::NowTime() {
    // std::cerr << "entering Log::NowTime(), win32\n";

    const int MAX_LEN = 200;
    char buffer[MAX_LEN];
    if (GetTimeFormatA(LOCALE_USER_DEFAULT, 0, 0,
                       "HH':'mm':'ss", buffer, MAX_LEN) == 0)
        return "Error in NowTime()";

    char result[100] = {0};
    static DWORD first = GetTickCount();
    sprintf(result, "%s.%03ld", buffer, (long)(GetTickCount() - first) % 1000);
    return result;
}

#else

#include <sys/time.h>

std::string Log::NowTime() {
    // std::cerr << "entering Log::NowTime(), unix\n";

    char buffer[11];
    time_t t;
    time(&t);
    tm r = {0};
    strftime(buffer, sizeof(buffer), "%X", localtime_r(&t, &r));

    // std::cerr << " time buffer=\"" << buffer << "\"\n";

    struct timeval tv;
    gettimeofday(&tv, 0);
    char result[100] = {0};
    std::sprintf(result, "%s.%03ld", buffer, (long)tv.tv_usec / 1000);

    // std::cerr << " time result=\"" << result << "\"\n";

    return result;
}

#endif //WIN32
